/*
 * Copyright (C) 2011, EADS France
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.gmi.controller;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import xowl.gmi.model.cvssl.FloatDataPlaceholder;
import xowl.gmi.model.cvssl.IntegerDataPlaceholder;
import xowl.gmi.model.cvssl.StringDataPlaceholder;
import xowl.gmi.model.cvssl.Symbol;
import xowl.gmi.model.ast.ASTNode;
import xowl.gmi.model.ast.FloatToken;
import xowl.gmi.model.ast.IntegerToken;
import xowl.gmi.model.ast.StringToken;

/**
 *
 * @author Laurent WOUTERS
 */
public class ASTMatcher {
    private static interface Matcher {
        int match(List<ASTNode> children, int index);
        int release();
        Element getController(Controller controller, Element parent, boolean inline);
    }
    
    private static class MatchLabel implements Matcher {
        private xowl.gmi.model.cvssl.Label definition;
        public MatchLabel(xowl.gmi.model.cvssl.Label def) { definition = def; }
        @Override public int match(List<ASTNode> children, int index) { return 0; }
        @Override public int release() { return 0; }
        @Override public Element getController(Controller controller, Element parent, boolean inline) { return new Label(controller, parent, definition); }
    }
    private static class MatchShape implements Matcher {
        private xowl.gmi.model.cvssl.Shape definition;
        public MatchShape(xowl.gmi.model.cvssl.Shape def) { definition = def; }
        @Override public int match(List<ASTNode> children, int index) { return 0; }
        @Override public int release() { return 0; }
        @Override public Element getController(Controller controller, Element parent, boolean inline) { return new Shape(controller, parent, definition); }
    }
    private static class MatchImage implements Matcher {
        private xowl.gmi.model.cvssl.Image definition;
        public MatchImage(xowl.gmi.model.cvssl.Image def) { definition = def; }
        @Override public int match(List<ASTNode> children, int index) { return 0; }
        @Override public int release() { return 0; }
        @Override public Element getController(Controller controller, Element parent, boolean inline) { return new Image(controller, parent, definition); }
    }
    private static class MatchDataPlaceholder implements Matcher {
        private xowl.gmi.model.cvssl.DataPlaceholder definition;
        private ASTNode matched;
        public MatchDataPlaceholder(xowl.gmi.model.cvssl.DataPlaceholder def) { definition = def; }
        @Override public int match(List<ASTNode> children, int index) {
            if (index >= children.size())
                return -1;
            Symbol symbol = children.get(index).getSymbol();
            if (definition instanceof StringDataPlaceholder && symbol instanceof StringToken) {
                matched = children.get(index);
                return 1;
            }
            if (definition instanceof IntegerDataPlaceholder && symbol instanceof IntegerToken) {
                matched = children.get(index);
                return 1;
            }
            if (definition instanceof FloatDataPlaceholder && symbol instanceof FloatToken) {
                matched = children.get(index);
                return 1;
            }
            return -1;
        }
        @Override public int release() {
            matched = null;
            return 1;
        }
        @Override public Element getController(Controller controller, Element parent, boolean inline) { return new DataPlaceholder(controller, parent, definition, matched); }
    }
    private static class MatchConnector implements Matcher {
        private xowl.gmi.model.cvssl.Connector definition;
        private ASTNode matched;
        private Matcher legend;
        private ASTNode legendNode;
        private Matcher legendLeft;
        private ASTNode legendLeftNode;
        private Matcher legendRight;
        private ASTNode legendRightNode;
        public MatchConnector(xowl.gmi.model.cvssl.Connector def) { definition = def; }
        @Override public int match(List<ASTNode> children, int index) {
            if (index >= children.size())
                return -1;
            Symbol symbol = children.get(index).getSymbol();
            if (symbol != definition)
                return -1;
            matched = children.get(index);
            List<ASTNode> subs = new ArrayList<>(matched.getAllChildren());
            sort(subs, matched);
            if (definition.getLegend() != null) {
                if (subs.isEmpty()) return clear();
                legend = match(definition.getLegend(), subs.get(0));
                if (legend == null) return clear();
                legendNode = subs.get(0);
                subs.remove(0);
            }
            if (definition.getOrigin().getLegend() != null) {
                if (subs.isEmpty()) return clear();
                legendLeft = match(definition.getOrigin().getLegend(), subs.get(0));
                if (legendLeft == null) return clear();
                legendLeftNode = subs.get(0);
                subs.remove(0);
            }
            if (definition.getTarget().getLegend() != null) {
                if (subs.isEmpty()) return clear();
                legendRight = match(definition.getTarget().getLegend(), subs.get(0));
                if (legendRight == null) return clear();
                legendRightNode = subs.get(0);
                subs.remove(0);
            }
            if (!subs.isEmpty())
                return clear();
            return 1;
        }
        private Matcher match(xowl.gmi.model.cvssl.Variable var, ASTNode node) {
            if (node.getSymbol() != var)
                return null;
            List<ASTNode> subs = new ArrayList<>(node.getAllChildren());
            sort(subs, node);
            Matcher mn = getMatchNode(var.getDefinition());
            if (mn.match(subs, 0) != subs.size())
                return null;
            return mn;
        }
        private int clear() {
            matched = null;
            legend = null;
            legendLeft = null;
            legendRight = null;
            return -1;
        }
        @Override public int release() {
            matched = null;
            legend = null;
            legendLeft = null;
            legendRight = null;
            return 1;
        }
        @Override public Element getController(Controller controller, Element parent, boolean inline) {
            if (matched == null)
                return null;
            GraphConnector connector = new GraphConnector(controller, parent, matched);
            if (legend != null) {
                Variable variable = new Variable(controller, connector, legendNode, true);
                variable.setChild(legend.getController(controller, variable, true));
                connector.setLegend(variable);
            }
                
            return connector;
        }
    }
    private static class MatchNode implements Matcher {
        private xowl.gmi.model.cvssl.Node definition;
        private ASTNode matched;
        private Matcher sub;
        public MatchNode(xowl.gmi.model.cvssl.Node def) { definition = def; }
        @Override public int match(List<ASTNode> children, int index) {
            if (index >= children.size())
                return -1;
            Symbol symbol = children.get(index).getSymbol();
            if (symbol != definition.getVariable())
                return -1;
            matched = children.get(index);
            sub = getMatchNode(definition.getVariable().getDefinition());
            List<ASTNode> subs = new ArrayList<>(matched.getAllChildren());
            sort(subs, matched);
            if (sub.match(subs, 0) != subs.size()) {
                matched = null;
                sub = null;
                return -1;
            }
            return 1;
        }
        @Override public int release() {
            matched = null;
            sub = null;
            return 1;
        }
        @Override public Element getController(Controller controller, Element parent, boolean inline) {
            GraphNode node = new GraphNode(controller, parent, definition, matched);
            Element inner = sub.getController(controller, node, true);
            node.setChild(inner);
            return node;
        }
    }
    private static class MatchVariableRef implements Matcher {
        private xowl.gmi.model.cvssl.VariableRef definition;
        private ASTNode matched;
        private Matcher sub;
        public MatchVariableRef(xowl.gmi.model.cvssl.VariableRef def) { definition = def; }
        @Override public int match(List<ASTNode> children, int index) {
            if (index >= children.size())
                return -1;
            Symbol symbol = children.get(index).getSymbol();
            if (symbol != definition.getVariable())
                return -1;
            matched = children.get(index);
            sub = getMatchNode(definition.getVariable().getDefinition());
            List<ASTNode> subs = new ArrayList<>(matched.getAllChildren());
            sort(subs, matched);
            if (sub.match(subs, 0) != subs.size()) {
                matched = null;
                sub = null;
                return -1;
            }
            return 1;
        }
        @Override public int release() {
            matched = null;
            sub = null;
            return 1;
        }
        @Override public Element getController(Controller controller, Element parent, boolean inline) {
            if (matched == null)
                return null;
            Variable var = new Variable(controller, parent, matched, inline);
            Element inner = sub.getController(controller, var, true);
            var.setChild(inner);
            return var;
        }
    }
    private static class MatchConcat implements Matcher {
        private xowl.gmi.model.cvssl.Concat definition;
        private List<Matcher> children;
        public MatchConcat(xowl.gmi.model.cvssl.Concat def) {
            definition = def;
            children = new ArrayList<>();
            List<xowl.gmi.model.cvssl.ConcatElem> subs = new ArrayList<>(def.getAllConcatElems());
            Collections.sort(subs, new Comparator<xowl.gmi.model.cvssl.ConcatElem>() {
                public int compare(xowl.gmi.model.cvssl.ConcatElem o1, xowl.gmi.model.cvssl.ConcatElem o2) {
                    return o1.getIndex() - o2.getIndex();
                }
            });
            for (xowl.gmi.model.cvssl.ConcatElem sub : subs)
                children.add(getMatchNode(sub.getElement()));
        }
        @Override public int match(List<ASTNode> nodes, int index) {
            int current = 0;
            int matched = 0;
            while (current != children.size()) {
                int result = children.get(current).match(nodes, index + matched);
                if (result == -1) {
                    int released = 0;
                    while (released == 0) {
                        if (current == 0)
                            return -1;
                        current--;
                        released = children.get(current).release();
                    }
                    matched -= released;
                    current++;
                } else {
                    matched += result;
                    current++;
                }
            }
            return matched;
        }
        @Override public int release() {
            for (int i=children.size()-1; i!=-1; i--) {
                int released = children.get(i).release();
                if (released != 0)
                    return released;
            }
            return 0;
        }
        @Override public Element getController(Controller controller, Element parent, boolean inline) {
            Concat concat = new Concat(controller, parent, definition);
            for (Matcher mn : children)
                concat.addChild(mn.getController(controller, concat, inline));
            return concat;
        }
    }
    private static class MatchOptional implements Matcher {
        private xowl.gmi.model.cvssl.Optional definition;
        private Matcher child;
        private int matched;
        public MatchOptional(xowl.gmi.model.cvssl.Optional def) { definition = def; }
        @Override public int match(List<ASTNode> nodes, int index) {
            if (child != null)
                return -1;
            Matcher temp = getMatchNode(definition.getElement());
            matched = temp.match(nodes, index);
            if (matched <= 0)
                return 0;
            child = temp;
            return matched;
        }
        @Override public int release() {
            if (child == null)
                return 0;
            int released = child.release();
            matched -= released;
            if (matched == 0)
                child = null;
            return released;
        }
        @Override public Element getController(Controller controller, Element parent, boolean inline) {
            Optional optional = new Optional(controller, parent, definition);
            if (child != null)
                optional.setChild(child.getController(controller, optional, inline));
            return optional;
        }
    }
    private static class MatchOneMore implements Matcher {
        private xowl.gmi.model.cvssl.OneMore definition;
        private List<Matcher> children;
        private List<Integer> matched;
        public MatchOneMore(xowl.gmi.model.cvssl.OneMore def) {
            definition = def;
            children = new ArrayList<>();
            matched = new ArrayList<>();
        }
        @Override public int match(List<ASTNode> nodes, int index) {
            int total = 0;
            Matcher temp = getMatchNode(definition.getElement());
            int result = temp.match(nodes, index + total);
            if (result == -1)
                return -1;
            while (result > 0) {
                children.add(temp);
                matched.add(result);
                total += result;
                temp = getMatchNode(definition.getElement());
                result = temp.match(nodes, index + total);
            }
            return total;
        }
        @Override public int release() {
            if (children.size() == 1)
                return 0;
            int temp = matched.get(children.size()-1);
            int released = children.get(children.size()-1).release();
            temp -= released;
            if (temp == 0) {
                children.remove(children.size()-1);
                matched.remove(matched.size()-1);
            } else {
                matched.set(matched.size()-1, temp);
            }
            return released;
        }
        @Override public Element getController(Controller controller, Element parent, boolean inline) {
            OneMore onemore = new OneMore(controller, parent, definition);
            for (Matcher mn : children)
                onemore.addChild(mn.getController(controller, onemore, inline));
            return onemore;
        }
    }
    private static class MatchZeroMore implements Matcher {
        private xowl.gmi.model.cvssl.ZeroMore definition;
        private List<Matcher> children;
        private List<Integer> matched;
        public MatchZeroMore(xowl.gmi.model.cvssl.ZeroMore def) {
            definition = def;
            children = new ArrayList<>();
            matched = new ArrayList<>();
        }
        @Override public int match(List<ASTNode> nodes, int index) {
            int total = 0;
            Matcher temp = getMatchNode(definition.getElement());
            int result = temp.match(nodes, index + total);
            if (result == -1)
                return 0;
            while (result > 0) {
                children.add(temp);
                matched.add(result);
                total += result;
                temp = getMatchNode(definition.getElement());
                result = temp.match(nodes, index + total);
            }
            return total;
        }
        @Override public int release() {
            if (children.isEmpty())
                return 0;
            int temp = matched.get(children.size()-1);
            int released = children.get(children.size()-1).release();
            temp -= released;
            if (temp == 0) {
                children.remove(children.size()-1);
                matched.remove(matched.size()-1);
            } else {
                matched.set(matched.size()-1, temp);
            }
            return released;
        }
        @Override public Element getController(Controller controller, Element parent, boolean inline) {
            ZeroMore zeromore = new ZeroMore(controller, parent, definition);
            for (Matcher mn : children)
                zeromore.addChild(mn.getController(controller, zeromore, inline));
            return zeromore;
        }
    }
    private static class MatchGraph implements Matcher {
        private xowl.gmi.model.cvssl.Graph definition;
        private List<Matcher> children;
        public MatchGraph(xowl.gmi.model.cvssl.Graph def) {
            definition = def;
            children = new ArrayList<>();
        }
        @Override public int match(List<ASTNode> nodes, int index) {
            int total = 0;
            while (matchInner(nodes, index + total))
                total++;
            return total;
        }
        private boolean matchInner(List<ASTNode> nodes, int index) {
            if (index >= nodes.size())
                return false;
            xowl.gmi.model.cvssl.Symbol symbol = nodes.get(index).getSymbol();
            for (xowl.gmi.model.cvssl.Node gn : definition.getAllNodes()) {
                if (gn.getVariable() != symbol)
                    continue;
                Matcher sub = new MatchNode(gn);
                if (sub.match(nodes, index) == -1)
                    return false;
                children.add(sub);
                return true;
            }
            for (xowl.gmi.model.cvssl.Connector connector : definition.getAllConnectors()) {
                if (connector != symbol)
                    continue;
                Matcher sub = new MatchConnector(connector);
                if (sub.match(nodes, index) == -1)
                    return false;
                children.add(sub);
                return true;
            }
            return false;
        }
        @Override public int release() {
            if (children.isEmpty())
                return 0;
            children.remove(children.size()-1);
            return 1;
        }
        @Override public Element getController(Controller controller, Element parent, boolean inline) {
            Graph graph = new Graph(controller, parent, definition);
            List<GraphConnector> connectors = new ArrayList<>();
            for (Matcher mn : children) {
                Element temp = mn.getController(controller, graph, false);
                if (temp instanceof GraphConnector)
                    connectors.add((GraphConnector)temp);
                else
                    graph.addChild((GraphNode)temp);
            }
            for (GraphConnector connector : connectors)
                graph.addChild(connector);
            return graph;
        }
    }
    private static class MatchUnion implements Matcher {
        private xowl.gmi.model.cvssl.Union definition;
        private List<xowl.gmi.model.cvssl.Element> options;
        private Matcher child;
        private int matched;
        public MatchUnion(xowl.gmi.model.cvssl.Union def) {
            definition = def;
            options = new ArrayList<>(def.getAllElements());
        }
        @Override public int match(List<ASTNode> nodes, int index) {
            if (child != null)
                return -1;
            for (xowl.gmi.model.cvssl.Element option : options) {
                Matcher temp = getMatchNode(option);
                matched = temp.match(nodes, index);
                if (matched == -1)
                    continue;
                child = temp;
                return matched;
            }
            return -1;
        }
        @Override public int release() {
            if (child == null)
                return 0;
            int released = child.release();
            matched -= released;
            if (matched == 0)
                child = null;
            return released;
        }
        @Override public Element getController(Controller controller, Element parent, boolean inline) {
            if (child == null)
                return null;
            return child.getController(controller, parent, inline);
        }
    }
    
    private static Matcher getMatchNode(xowl.gmi.model.cvssl.Element element) {
        if (element instanceof xowl.gmi.model.cvssl.Label)
            return new MatchLabel((xowl.gmi.model.cvssl.Label)element);
        if (element instanceof xowl.gmi.model.cvssl.Shape)
            return new MatchShape((xowl.gmi.model.cvssl.Shape)element);
        if (element instanceof xowl.gmi.model.cvssl.Image)
            return new MatchImage((xowl.gmi.model.cvssl.Image)element);
        if (element instanceof xowl.gmi.model.cvssl.DataPlaceholder)
            return new MatchDataPlaceholder((xowl.gmi.model.cvssl.DataPlaceholder)element);
        if (element instanceof xowl.gmi.model.cvssl.Graph)
            return new MatchGraph((xowl.gmi.model.cvssl.Graph)element);
        if (element instanceof xowl.gmi.model.cvssl.VariableRef)
            return new MatchVariableRef((xowl.gmi.model.cvssl.VariableRef)element);
        if (element instanceof xowl.gmi.model.cvssl.Concat)
            return new MatchConcat((xowl.gmi.model.cvssl.Concat)element);
        if (element instanceof xowl.gmi.model.cvssl.Optional)
            return new MatchOptional((xowl.gmi.model.cvssl.Optional)element);
        if (element instanceof xowl.gmi.model.cvssl.OneMore)
            return new MatchOneMore((xowl.gmi.model.cvssl.OneMore)element);
        if (element instanceof xowl.gmi.model.cvssl.ZeroMore)
            return new MatchZeroMore((xowl.gmi.model.cvssl.ZeroMore)element);
        if (element instanceof xowl.gmi.model.cvssl.Union)
            return new MatchUnion((xowl.gmi.model.cvssl.Union)element);
        return null;
    }
    
    
    public static Element buildControlTree(Controller controller, Element parent, xowl.gmi.model.cvssl.Element element, ASTNode node, boolean inline) {
        List<ASTNode> list = new ArrayList<>();
        list.add(node);
        return buildControlTree(controller, parent, element, list, inline);
    }
    public static Element buildControlTree(Controller controller, Element parent, xowl.gmi.model.cvssl.Node graphNode, ASTNode node) {
        List<ASTNode> list = new ArrayList<>();
        list.add(node);
        MatchNode sub = new MatchNode(graphNode);
        if (sub.match(list, 0) != 1)
            return null;
        return sub.getController(controller, parent, true);
    }
    public static Element buildControlTree(Controller controller, Element parent, xowl.gmi.model.cvssl.Connector connector, ASTNode node) {
        List<ASTNode> list = new ArrayList<>();
        list.add(node);
        MatchConnector sub = new MatchConnector(connector);
        if (sub.match(list, 0) != 1)
            return null;
        return sub.getController(controller, parent, true);
    }
    public static Element buildControlTree(Controller controller, Element parent, xowl.gmi.model.cvssl.Element element, List<ASTNode> nodes, boolean inline) {
        try {
            Matcher mn = getMatchNode(element);
            int result = mn.match(nodes, 0);
            if (result != nodes.size())
                return null;
            return mn.getController(controller, parent, inline);
        } catch (java.lang.Exception ex) {
            controller.getLog().fatal(Controller.name, "GMI AST matcher failed!", ex);
            return null;
        }
    }
    
    public static boolean matches(xowl.gmi.model.cvssl.Element spec, xowl.gmi.model.cvssl.Symbol symbol) {
        if (spec == symbol) return true;
        if (spec instanceof xowl.gmi.model.cvssl.Union)
            return (matches((xowl.gmi.model.cvssl.Union)spec, symbol));
        else if (spec instanceof xowl.gmi.model.cvssl.Optional)
            return (matches((xowl.gmi.model.cvssl.Optional)spec, symbol));
        else if (spec instanceof xowl.gmi.model.cvssl.ZeroMore)
            return (matches((xowl.gmi.model.cvssl.ZeroMore)spec, symbol));
        else if (spec instanceof xowl.gmi.model.cvssl.OneMore)
            return (matches((xowl.gmi.model.cvssl.OneMore)spec, symbol));
        else if (spec instanceof xowl.gmi.model.cvssl.VariableRef)
            return (matches((xowl.gmi.model.cvssl.VariableRef)spec, symbol));
        return false;
    }
    public static boolean matches(xowl.gmi.model.cvssl.Union spec, xowl.gmi.model.cvssl.Symbol symbol) {
        for (xowl.gmi.model.cvssl.Element child : spec.getAllElements()) {
            if (matches(child, symbol))
                return true;
        }
        return false;
    }
    public static boolean matches(xowl.gmi.model.cvssl.Optional spec, xowl.gmi.model.cvssl.Symbol symbol) {
        return (matches(spec.getElement(), symbol));
    }
    public static boolean matches(xowl.gmi.model.cvssl.ZeroMore spec, xowl.gmi.model.cvssl.Symbol symbol) {
        return (matches(spec.getElement(), symbol));
    }
    public static boolean matches(xowl.gmi.model.cvssl.OneMore spec, xowl.gmi.model.cvssl.Symbol symbol) {
        return (matches(spec.getElement(), symbol));
    }
    public static boolean matches(xowl.gmi.model.cvssl.VariableRef spec, xowl.gmi.model.cvssl.Symbol symbol) {
        return (spec.getVariable() == symbol);
    }
    
    public static void sort(List<ASTNode> nodes, final ASTNode parent) {
        Collections.sort(nodes, new Comparator<xowl.gmi.model.ast.ASTNode>() {
            @Override public int compare(xowl.gmi.model.ast.ASTNode node1, xowl.gmi.model.ast.ASTNode node2) {
                Integer index1 = null;
                Integer index2 = null;
                for (xowl.gmi.model.ast.Decorator decorator : node1.getAllDecorators())
                    if (decorator.getParent() == parent)
                        index1 = decorator.getIndex();
                for (xowl.gmi.model.ast.Decorator decorator : node2.getAllDecorators())
                    if (decorator.getParent() == parent)
                        index2 = decorator.getIndex();
                if (index1==null || index2==null) return 0;
                return (index1 - index2);
            }
        });
    }
}
